<?php
/**
 * @author Erling Owe <erling.owe@gmail.com>
 * @package Core
 * @subpackage Presentation
 */

class Core_Presentation_HTML_Element {

    private $Document;
    private $NodeCollection;

    private $strTagName;
    private $arrAttributes = array(); //suffix collection


    function __construct ( $objDocument, $strTagName ) {
        $this->Document = $objDocument;
        $this->NodeCollection = new Core_Presentation_HTML_NodeCollection ();
        $this->strTagName = $strTagName;
    }

    /**
     * Gets the elements tag name.
     *
     * @return string The elements tag name.
     */
    function __toString () {
        return $this->strTagName;
    }

    /**
     * Checks whether or not this element has an end tag.
     *
     * @return boolean
     */
    private function hasEndTag () {
        $arr = array( "base", "br", "hr", "img", "link");
        return in_array( $this, $arr ) ? false : true;
    }

    public function allowedChildren () {

    }

    public function allowedAttributes () {

        $arr = array(

            "p"     =>      array( "id" )

        );

    }

    public function attributes () {
        return (object) $this->arrAttributes;
    }

    public function hasAttributes () {
        return ( count( $this->attributes() ) ) ? true : false;
    }

    /**
     * Gets a collection of all the children of this element.
     *
     * @param string $strExpression Selectors.
     * @return Core_Presentation_HTML_NodeCollection
     */
    public function children ( $strExpression = null ) {
        return $this->NodeCollection->all( $strExpression );
    }

    /**
     * Checks whether or not this element has any children.
     *
     * @return boolean
     */
    public function hasChildren () {
        return ( count( $this->NodeCollection() ) ) ? true : false;
    }

    /**
     * Gets the parent element of this element.
     *
     * @return Core_Presentation_HTML_Element
     */
    public function parent () {
        if ( in_array( $this, $this->Document->children() ) ) {
            return $this->Document;
        } else {
            foreach ( $this->Document->find() as $objNode ) {
                if ( $objNode instanceof Core_Presentation_HTML_Element ) {
                    if ( in_array( $this, $objNode->children() ) ) {
                        return $objNode;
                    }
                }
            }
        }
    }

    /**
     * Adds a child to the element.
     *
     * @param Core_Presentation_HTML_Element $objElement
     */
    public function addChild ( $objNode ) {
        $this->NodeCollection[] = $objNode;
    }

    public function removeChild () {

    }

    /**
     * Sets an attribute for the element.
     *
     * @param string $strName
     * @param string $strValue
     */
    public function setAttribute ( $strName, $strValue ) {
        $objAttribute = new Core_Presentation_HTML_Attribute( $this, $strName, $strValue );
        $this->arrAttributes[$strName] = $objAttribute;
    }

    /**
     * Gets the value of an attribute.
     *
     * @param string $strName
     * @return boolean|string The value if the attribute is set, false if not.
     */
    public function getAttribute ( $strName ) {
        if ( isset( $this->arrAttributes[$strName] ) ) {
            return $this->arrAttributes[$strName];
        } else {
            return false;
        }
    }

    public function removeAttribute () {

    }

    /**
     * Gets the HTML for this element and all its descendants.
     *
     * @return string
     */
    public function getHTML () {

        // Start tag.
        $strReturn = "<" . $this;

        // Attributes.
        foreach ( $this->attributes() as $strName => $strValue ) {
            // TODO: Find out when to include quotes around the values.
            $strReturn .= " " . $strName . '="' . $strValue . '"';
        }

        // Closing the start tag.
        $strReturn .= ">";

        // Don't go to new line if the only child is a text node.
        if ( count( $this->children() ) == 0 ) {
            
        } elseif ( count( $this->children() ) == 1 ) {
            $objChild = $this->children()->getIterator()->current();
            if ( $objChild instanceof Core_Presentation_HTML_TextNode ) {
            } else {
                $strReturn .= "\r\n";
            }
        } else {
            $strReturn .= "\r\n";
        }

        // If the tag has an end tag we need to include it and it may also have children.
        if ( $this->hasEndTag() ) {

            // Get the HTML from the children.
            foreach ( $this->children() as $objChild ) {
                // The child may be either a text node, widget or an element.
                if ( $objChild instanceof Core_Presentation_HTML_Element ) {
                    $arrLines = explode( "\r\n", $objChild->getHTML() );
                    foreach ( $arrLines as $strLine ) {
                        $strReturn .= $this->Document->getTab() . $strLine . "\r\n";
                    }
                } else {
                    $strReturn .= $objChild;
                }
            }

            // Closing tag.
            $strReturn .= "</" . $this . ">";

        }

        // Return the HTML.
        return $strReturn;

    }

    /**
     * Imports HTML as element objects descending from this element.
     *
     * @param string $strHTML
     */
    public function importHTML ( $strHTML, $boolLoose = false ) {

        while ( $strHTML != "" ) {
            $this->importHTMLLoop( $this, $strHTML, $strHTML );
        }

    }

    private function importHTMLLoop ( $objParent, &$strHTML, $strHTMLCopy ) {

        while ( $strHTML != "" ) {

            // Anything but an element.
            $strPattern = "/^[^<>]+/";
            preg_match( $strPattern, $strHTML, $arrMatches );

            if ( $arrMatches ) {
                if ( trim( $arrMatches[0] ) ) {
                    $objTextNode = $this->Document->createTextNode( $arrMatches[0] );
                    $objTextNode->convertHTMLSpecialChars( false );
                    $objParent->addChild( $objTextNode );
                    //echo "Added text: " . $arrMatches[0] . "<br>"; # REMOVE THIS LINE
                }
                $strHTML = substr( $strHTML, strlen( $arrMatches[0] ) );
            }


            // An opening tag.
            // $strPattern = "/^<([a-z]+[123456]?)[^>]*>/";
            $strPattern = "/^<([a-z]+[123456]?)([^>]*)>/";
            preg_match( $strPattern, $strHTML, $arrMatches );

            if ( $arrMatches ) {
                $objElement = $this->Document->createElement( $arrMatches[1] );
                $objParent->addChild( $objElement );
                //echo "<br>\$strHTML: " . htmlspecialchars( $strHTML ) . "<br><br>"; # REMOVE THIS LINE
                //echo "Added element: " . $arrMatches[1] . "<br>"; # REMOVE THIS LINE

                // Add attributes.
                if ( $arrMatches[2] ) {
                    $strPattern = "/[\s]+/";
                    $arrAttributeStrings = preg_split( $strPattern, trim( $arrMatches[2] ) );
                    foreach ( $arrAttributeStrings as $strAttributeString ) {
                        $arr = explode( "=", $strAttributeString );
                        if ( substr( $arr[1], 0, 1) == '"' and substr( $arr[1], -1 ) == '"' ) {
                            $arr[1] = substr( $arr[1], 1, strlen( $arr[1] ) - 2 );
                        }
                        $objElement->setAttribute( $arr[0], $arr[1] );
                        //echo "Added attribute: " . $arr[0] . ", " . $arr[1] . "<br>"; # REMOVE THIS LINE
                    }
                }

                //echo "Removing: \"" . htmlspecialchars( $arrMatches[0] ) . "\"<br>"; # REMOVE THIS LINE
                $strHTML = substr( $strHTML, strlen( $arrMatches[0] ) );

                if ( $objElement->hasEndTag() ) {
                    $this->importHTMLLoop( $objElement, $strHTML, $strHTMLCopy );
                }

            }


            // A closing tag.
            $strPattern = "#^</([a-z]+[123456]?)>#";
            preg_match( $strPattern, $strHTML, $arrMatches );

            if ( $arrMatches and $arrMatches[1] == (string) $objParent ) {
                $strHTML = substr( $strHTML, strlen( $arrMatches[0] ) );
                //echo "Closing tag for: " . $arrMatches[1] . "<br>"; # REMOVE THIS LINE
                return;
            } elseif ( $arrMatches ) {
                throw new Exception ( "Unexpected end tag '" . $arrMatches[0] . "', expecting '</" . $objParent . ">', while importing the following HTML: " . $strHTMLCopy );
            }

        }

    }

}

?>
